//! 助记词，公私钥生成。
use crate::{
    core::{self, constant::MNEMONIC_WORD_SIZE},
    error::{Error, ErrorKind},
    utils,
};
use base64::prelude::BASE64_STANDARD;
use base64::Engine;
use bip39::{Language, Mnemonic as Bip39Mnemonic, MnemonicType};
use crypto::{digest::Digest, ripemd160::Ripemd160};
use hdwallet::{
    secp256k1::KeyPair,
    secp256k1::{PublicKey, Secp256k1},
    ExtendedPrivKey, KeyIndex,
};
use num_bigint::BigInt;
use serde_json::json;

pub mod private_key;

const ADDRESS_INDEX_LIMIT: u32 = 1024;

/// 助记词 -> seed -> 私钥 -> 公钥  -> 地址
#[derive(Debug, Clone)]
pub struct Mnemonic {
    // 最后生成的助记词
    mnemonic: String,
    // 助记词 生成seed， seed生成私钥
    seed: Vec<u8>,
    // 私钥链
    master_extend_private: ExtendedPrivKey,
}

impl Mnemonic {
    /// 生成助记词和seed和私钥
    /// words: 我们提供的助记词，如果验证通过，直接设置。
    /// passphrase: 密码，用来生成最终的seed的
    pub fn new(words: &str, passphrase: &str) -> Result<Self, Error> {
        let mnemonic_type = MnemonicType::for_word_count(MNEMONIC_WORD_SIZE as usize).unwrap();
        let mnemonic_code = Bip39Mnemonic::new(mnemonic_type, Language::English);
        let mnemonic_words = Self::generate_mnemonic(&mnemonic_code, words)?;
        let seed = Self::generate_seed(&mnemonic_code, passphrase);
        let private = Self::generate_private_key(&seed)?;
        Ok(Self {
            mnemonic: mnemonic_words,
            seed,
            master_extend_private: private,
        })
    }

    /// 生成keypair
    /// inve钱包地址采用BIP44提议 由定义生成钱包地址
    /// 定义的模板 ["sig", {"pubkey":""+ DSA.encryptBASE64(public) +""}]
    /// 公钥为  m44'/0'/0'/0 /0
    pub fn keypair(
        &self,
        purpose: u32,
        coin_type: u32,
        account: u32,
        is_change: u32,
        address_index: BigInt,
    ) -> Result<KeyPair, Error> {
        let pri =
            self.get_device_private_key(purpose, coin_type, account, is_change, address_index)?;
        Ok(KeyPair::from_secret_key(
            &Secp256k1::new(),
            &pri.private_key,
        ))
    }

    /// generate address
    pub fn get_address(&self) -> Result<String, Error> {
        self.get_new_addeess(0, 0u32.into())
    }

    pub fn get_new_addeess(&self, is_change: u32, address_index: BigInt) -> Result<String, Error> {
        let private: ExtendedPrivKey =
            self.get_device_private_key(44, 0, 0, is_change, address_index)?;
        let public = private
            .private_key
            .public_key(&Secp256k1::new())
            .serialize();
        let public_str = BASE64_STANDARD.encode(&public);
        let definition = json!(["sig", { "pubkey": public_str }]);
        let data = utils::sign::get_source_string(&definition)?;
        let address = Self::get_address_by_definition(&data)?;
        Ok(address.to_uppercase())
    }

    /// 生成自定义地址
    pub fn get_address_by_definition(definition: &str) -> Result<String, Error> {
        let mut digest = Ripemd160::new();
        let mut hash = [0u8; 20];
        digest.input_str(definition);
        digest.result(&mut hash);
        // 去除定义hash的前四位， 生成truncated hash
        let truncated_hash = &hash[4..];
        // 利用truncated hash生成校验码: truncated_hash -- sha256 -- 取5 13 21 29 位的字节为checksum
        let checksum = core::chash::get_checksum(truncated_hash);
        // 转换成二进制串
        let bin_clean_data = utils::byte_utils::bytes_to_binary_str(truncated_hash);
        let bin_checksum = utils::byte_utils::bytes_to_binary_str(&checksum);
        // 将校验位的二进制的每一位按照规律穿插进bin_clean_data的hash二进制中。
        //1,4,4,8,16,17,22,26,28,32,39,47,53,61,63,64,66,73,76,81,82,87,90,92,94,101,103,104,110,118,122,123
        let bin_chash = core::chash::mix_checksum_into_clean_data(&bin_clean_data, &bin_checksum);
        let chash = utils::byte_utils::binary_str_to_bytes(&bin_chash)?;
        // 对chash进行base32编码生成地址
        Ok(base32::encode(base32::Alphabet::Crockford, &chash))
    }

    pub fn generate_44_str(data: &[i32]) -> Result<String, Error> {
        if data.len() == 0 {
            return Ok(String::from("m"));
        }
        if data.len() > 5 {
            return Err(ErrorKind::ArgumentWrong("bip44 depth must <= 5".to_string()).into());
        }
        let mut ret = String::from("m/");
        for (index, v) in data.iter().enumerate() {
            if index < 3 {
                ret.push_str(&format!("{}'", v));
            } else {
                ret.push_str(&format!("{}", v));
            }
            ret.push_str("/");
        }
        let _ = ret.strip_suffix("/");
        Ok(ret)
    }

    pub fn mnemonic(&self) -> &str {
        &self.mnemonic
    }

    pub fn seed(&self) -> &[u8] {
        &self.seed
    }

    pub fn master_extended_private(&self) -> &ExtendedPrivKey {
        &self.master_extend_private
    }

    // 生成公钥
    pub fn generate_public_key(private_key: &ExtendedPrivKey) -> PublicKey {
        private_key.private_key.public_key(&Secp256k1::new())
    }

    pub fn get_device_private_key(
        &self,
        purpose: u32,
        coin_type: u32,
        account: u32,
        is_change: u32,
        mut address_index: BigInt,
    ) -> Result<ExtendedPrivKey, Error> {
        // m/purpose'/coin_type'/account'/is_change
        let mut account_pri = self
            .master_extend_private
            .derive_private_key(KeyIndex::hardened_from_normalize_index(purpose)?)?
            .derive_private_key(KeyIndex::hardened_from_normalize_index(coin_type)?)?
            .derive_private_key(KeyIndex::hardened_from_normalize_index(account)?)?
            .derive_private_key(KeyIndex::Normal(is_change))?;
        if address_index >= BigInt::from(ADDRESS_INDEX_LIMIT) {
            while address_index >= BigInt::from(ADDRESS_INDEX_LIMIT) {
                let child_index = &address_index % ADDRESS_INDEX_LIMIT;
                address_index = &address_index / ADDRESS_INDEX_LIMIT;
                let (_, bytes) = address_index.to_u32_digits();
                let high_u32 = bytes[bytes.len() - 1];
                account_pri = account_pri
                    .derive_private_key(high_u32.into())?
                    .derive_private_key(child_index.to_u32_digits().1[0].into())?;
            }
            return Ok(account_pri);
        }
        if address_index == BigInt::from(0) {
            Ok(account_pri.derive_private_key(0.into())?)
        } else {
            Ok(account_pri.derive_private_key(address_index.to_u32_digits().1[0].into())?)
        }
    }

    // 生成短语
    fn generate_mnemonic(mnemonic: &Bip39Mnemonic, words: &str) -> Result<String, Error> {
        if !words.is_empty() {
            let mt = MnemonicType::for_phrase(words)
                .map_err(|e| Error::from_kind(ErrorKind::Bip39Mnemonic(e.to_string())))?;
            if mt.word_count() != MNEMONIC_WORD_SIZE {
                return Err(ErrorKind::Bip39Mnemonic("mnemonic length error!".to_string()).into());
            }
            return Ok(words.to_string());
        }
        // words为空，需要我们生成
        Ok(mnemonic.phrase().to_string())
    }

    // 生成seed
    fn generate_seed(mnemonic: &Bip39Mnemonic, password: &str) -> Vec<u8> {
        bip39::Seed::new(mnemonic, password).as_bytes().to_vec()
    }

    // 生成私钥
    fn generate_private_key(seed: &[u8]) -> Result<ExtendedPrivKey, Error> {
        Ok(hdwallet::ExtendedPrivKey::with_seed(seed)?)
    }
}

#[cfg(test)]
mod tests {
    use super::Mnemonic;
    use hdwallet::KeyIndex;
    #[test]
    fn mnemonic_new_test() {
        let mnemonic = Mnemonic::new("", "wawa").unwrap();
        let kp = mnemonic.keypair(44, 0, 0, 0, 10.into()).unwrap();
        let kp_private = kp.secret_bytes();
        println!("mnemonic: {}", mnemonic.mnemonic());

        let generate_pri = mnemonic
            .master_extend_private
            .derive_private_key(KeyIndex::hardened_from_normalize_index(44).unwrap())
            .unwrap()
            .derive_private_key(KeyIndex::hardened_from_normalize_index(0).unwrap())
            .unwrap()
            .derive_private_key(KeyIndex::hardened_from_normalize_index(0).unwrap())
            .unwrap()
            .derive_private_key(0.into())
            .unwrap()
            .derive_private_key(10.into())
            .unwrap();
        assert_eq!(kp_private, generate_pri.private_key.secret_bytes());
    }
}
